package com.starleague.jpa.appengine;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.common.collect.ImmutableList;

import com.starleague.jpa.JpaTransaction;

// import java.lang.reflect.Constructor;
// import java.lang.reflect.AnnotatedElement;
// import javax.persistence.*;
// import java.net.URLEncoder;
// import java.net.URLDecoder;

// TODO: Convert all Runtime(Ninja) to JpaExceptions/GaeExceptions, especially on commit/rollback/persist/find/...
public class GaeTransaction
        implements JpaTransaction<Key, GaeDao>
{
    private static final Logger log = LoggerFactory.getLogger(GaeTransaction.class);

    private final EntityManager entityManager;
    private EntityTransaction transaction;

    GaeTransaction(EntityManager entityManager)
    {
        this.entityManager = entityManager;
        // this.entityManager.setFlushMode(FlushModeType.COMMIT);

        this.transaction = this.entityManager.getTransaction();
        this.transaction.begin();
    }

    @Override
    public synchronized void commit()
    {
        if ((this.transaction != null) && this.transaction.isActive())
        {
            this.entityManager.flush();

            this.transaction.commit();
            this.transaction = null;
        }
    }

    @Override
    public synchronized void rollback()
    {
        if ((this.transaction != null) && this.transaction.isActive())
        {
            this.entityManager.flush();

            this.transaction.rollback();
            this.transaction = null;
        }
    }

    @Override
    public synchronized void close()
    {
        try
        {
            rollback();
        }
        catch (final Exception e)
        {
            log.error("close() rollback failed: ", e);
        }
    }

    @Override
    public synchronized <GAE_DAO extends GaeDao> GAE_DAO findById(Class<GAE_DAO> gaeDaoClass,
                                                                  Key gaeDaoKey)
    {
        return this.entityManager.find(gaeDaoClass, gaeDaoKey);
    }

    @Override
    public synchronized <GAE_DAO extends GaeDao> GAE_DAO findByUid(Class<GAE_DAO> gaeDaoClass,
                                                                   String dtoUid)
    {
        return this.entityManager.find(gaeDaoClass, KeyFactory.stringToKey(dtoUid));
    }

    // @Override
    @SuppressWarnings("unchecked")
    public synchronized <GAE_DAO extends GaeDao> List<GAE_DAO> findAllChildren(Class<GAE_DAO> gaeDaoClass,
                                                                               Key ancestorKey)
    {
        try
        {
            Query query = this.entityManager.createQuery("select from "
                                                         + gaeDaoClass.getName()
                                                         + " gae where gae."
                                                         + GaeDao.SQL.FIELD_ANCESTOR_KEY
                                                         + "=:"
                                                         + GaeDao.SQL.FIELD_ANCESTOR_KEY, gaeDaoClass);
            query.setParameter(GaeDao.SQL.FIELD_ANCESTOR_KEY, ancestorKey);
            return query.getResultList();
        }
        catch (final NoResultException e)
        {
            return ImmutableList.of();
        }
    }

    // @Override
    @SuppressWarnings("unchecked")
    public synchronized <GAE_DAO extends GaeDao> List<Key> findAllIds(Class<GAE_DAO> gaeDaoClass)
    {
        try
        {
            Query query = this.entityManager.createQuery("select "
                                                         + GaeDao.SQL.FIELD_ID_KEY
                                                         + " from "
                                                         + gaeDaoClass.getName()
                                                         + " gae", Key.class);
            return query.getResultList();
        }
        catch (final NoResultException e)
        {
            return ImmutableList.of();
        }
    }

    @Override
    public synchronized void persist(GaeDao gaeDao)
    {
        if (!this.entityManager.contains(gaeDao))
        {
            this.entityManager.persist(gaeDao);
        }
        this.entityManager.flush();
    }

    public synchronized void remove(GaeDao... gaeDaos)
    {
        // log.info("remove(): Removing Objects [{}]", gaeDaos.length);
        for (final GaeDao gaeDao : gaeDaos)
        {
            this.entityManager.remove(gaeDao);
        }
        this.entityManager.flush();
    }

    public void refresh(GaeDao... gaeDaos)
    {
        for (final GaeDao gaeDao : gaeDaos)
        {
            this.entityManager.refresh(gaeDao);
        }
        this.entityManager.flush();
    }

    @Override
    public synchronized <GAE_DAO extends GaeDao> GAE_DAO merge(GAE_DAO gaeDao)
    {
        if (!this.entityManager.contains(gaeDao))
        {
            final GAE_DAO newGaeDao = this.entityManager.merge(gaeDao);
            this.entityManager.flush();
            return newGaeDao;
        }
        // else
        return gaeDao;
    }

    // public Query createNamedQuery(final String i_queryName)
    // {
    // return this.entityManager.createNamedQuery(i_queryName);
    // }
    //
    // public Query createQuery(final String i_queryStr)
    // {
    // return this.entityManager.createQuery(i_queryStr);
    // }
}
//
// public <T> T persist(final T ... i_objs) throws Throwable
// {
// EntityTransaction transaction = this.entityManager.getTransaction();
//
// try
// {
// log.info("persist(): Starting Transaction");
// transaction.begin();
//
// for (final T obj : i_objs)
// {
// try
// {
// final T newObj = this.entityManager.merge(obj);
// this.entityManager.persist(newObj);
// this.entityManager.flush();
// }
// catch(javax.persistence.EntityExistsException e)
// {
// log.warn("Got Error: " + e.getMessage());
// e.printStackTrace();
// }
// catch(Throwable e)
// {
// log.warn("Got Error: " + e.getMessage());
// e.printStackTrace();
// throw new PersistException(e);
// }
// }
//
// // this.entityManager.flush();
//
// log.info("persist(): Commiting Transaction");
// transaction.commit();
// transaction = null;
//
// return true;
// }
// catch(final RollbackException e)
// {
// throw e.getCause();
// }
// finally
// {
// if (transaction != null)
// try { transaction.rollback(); } catch(final Throwable e) {}
// }
// }
//
// public <T> boolean remove(final T ... i_objs) throws Throwable
// {
// EntityTransaction transaction = this.entityManager.getTransaction();
//
// try
// {
// log.info("remove(): Starting Transaction");
// transaction.begin();
//
// for (final T obj : i_objs)
// {
// try
// {
// this.entityManager.remove(obj);
// this.entityManager.flush();
// }
// catch(javax.persistence.EntityExistsException e)
// {
// log.warn("Got Error: " + e.getMessage());
// e.printStackTrace();
// }
// catch(Throwable e)
// {
// log.warn("Got Error: " + e.getMessage());
// e.printStackTrace();
// throw new PersistException(e);
// }
// }
//
// // this.entityManager.flush();
//
// log.info("remove(): Commiting Transaction");
// transaction.commit();
// transaction = null;
//
// return true;
// }
// catch(final RollbackException e)
// {
// throw e.getCause();
// }
// finally
// {
// if (transaction != null)
// try { transaction.rollback(); } catch(final Throwable e) {}
// }
// }
//
// // public boolean refresh(final Object ... i_objects) throws Throwable
// // {
// // EntityTransaction transaction = this.entityManager.getTransaction();
// //
// // try
// // {
// // log.info("Starting Transaction");
// // transaction.begin();
// //
// // for (Object obj : i_objects)
// // DataAccessObject.refresh(this.entityManager, obj);
// //
// //// this.entityManager.flush();
// //
// // log.info("Commiting Transaction");
// // transaction.commit();
// // transaction = null;
// //
// // return true;
// // }
// // catch(final RollbackException e)
// // {
// // throw e.getCause();
// // }
// // finally
// // {
// // if (transaction != null)
// // try { transaction.rollback(); } catch(final Throwable e) {}
// // }
// // }
//
// private final EntityManager entityManager;
//
// private static final Logger log = Logger.getLogger(PersistManager.class);

//
//
//
// //Annotation[] classAnnotations = obj.getClass().getAnnotations();
// //for(Annotation classAnnotation : classAnnotations)
// //{
// // if (classAnnotation instanceof DataAccessObject.DaoClass)
// // {
// // Class<? extends DataAccessObject> daoClass =
// // ((DataAccessObject.DaoClass)classAnnotation).value();
// //
// // DataAccessObject.Manager dao_manager = classDaoFactoryMap.get(daoClass);
// //
// // if (factory == null)
// // {
// // factory = this.createFactory(daoClass);
// // classDaoFactoryMap.put(daoClass, factory);
// // log.error("Created factory [" + factory + "] for DAO class [" + daoClass +
// "]");
// // }
// //
// // dao = factory.create(obj);
// // objectDaoMap.put(obj, dao);
// // log.error("Created dao [" + dao + "]");
// //
// // break;
// // }
// //}
// //DataAccessObject dao = objectDaoMap.get(obj);
// //
// //if (dao == null)
// ////{
// // Annotation[] classAnnotations = obj.getClass().getAnnotations();
// // for(Annotation classAnnotation : classAnnotations)
// // {
// // if (classAnnotation instanceof DataAccessObject.DaoClass)
// // {
// Class<? extends DataAccessObject> daoClass =
// ((DataAccessObject.DaoClass)classAnnotation).value();
//
// DataAccessObject.Factory factory = classDaoFactoryMap.get(daoClass);
//
// if (factory == null)
// {
// factory = this.createFactory(daoClass);
// classDaoFactoryMap.put(daoClass, factory);
// log.error("Created factory [" + factory + "] for DAO class [" + daoClass +
// "]");
// }
//
// dao = factory.create(obj);
// objectDaoMap.put(obj, dao);
// log.error("Created dao [" + dao + "]");
//
// break;
// }
// }
